import datetime
import re
from os import stat, makedirs, rename, umask
from os.path import expanduser
from subprocess import Popen
import subprocess
import socket
import os
import glob
import random
import dochallenge


testing = socket.gethostname() != 'pc.cs.purdue.edu'

if testing:
    TRACES_DIR = 'applications/icypc/private/traces'
else:
    TRACES_DIR = '/homes/cs390cp/traces'
    ICYPC_JAR = '/homes/cs390cp/icypc/icypc.jar'
    MAPS_DIR = '/homes/cs390cp/icypc/maps'

def chooseMap():
    files = glob.glob(os.path.join(MAPS_DIR, '*.txt'))
    return random.choice(files)

def makeTraceDir(logger):
    try:
        mode = stat(TRACES_DIR).st_mode
        if (mode & 0040000 == 0):  # S_IFDIR = 0040000
            logger.critical('%s is not a directory or not accessible -- tell a friend' % TRACES_DIR)
    except OSError:
        try:
            makedirs(TRACES_DIR)
        except:
            logger.error('could not create %s == tell a friend' % TRACES_DIR)

def getMatch():
    ladderadjust = local_import('ladderadjust', reload=True)
    ladder = ladderadjust.buildLadder(db)
    challengers = db(db.competitor.status == 'ready').select(db.competitor.ALL, orderby=db.competitor.id)
    
    for challenger in challengers:
        # find a suitable challenger
        (redUser, redPlayer) = (challenger.name_user, challenger.name_player)
        try:
            index = ladder.index((redUser, redPlayer))
        except ValueError:
            # Add player to ladder.  A bit odd, since the player has not yet been in match,
            # so if the ladder is rebuilt, the player won't be in it again.
            # Causes odd behavior if first two competitors tie, their match is removed and they
            # do not appear in the ladder (and both are marked as 'rested'.  For another day.
            logger.info('\t* Player (%s/%s) not in ladder, adding.' % (redUser, redPlayer))
            ladder.append((redUser, redPlayer))
            index = ladder.index((redUser, redPlayer))

        # find corresponding defender
        if index == 0:  # challenger is already number 1; just ignore
            challenger.update_record(status='resting')
            db.commit()
            logger.info('\t* Challenger (%s/%s) at top of ladder; resting.' % (redUser, redPlayer))
        else:
            defender = ladder[index - 1]
            (blueUser, bluePlayer) = defender
            return (redUser, redPlayer, blueUser, bluePlayer, challenger)

output_pattern = re.compile(r'.*(Winner: (\d+)|Tie)\sScore: (\d+) \((\d+) (\d+)\) (\d+) \((\d+) (\d+)\)', re.DOTALL)

def runMatch(match):
    # Identify the contenders with several convenience variables...
    (redUser, redPlayer, blueUser, bluePlayer, challenger) = match
    
    # Mark match as in progress...
    now = datetime.datetime.now()
    id = db.match.insert(time=now)
    db.commit()
    logger.info("\n===== BEGIN MATCH %d at %s =====" % (id, now))
    logger.info("\t%s/%s (red) challenging %s/%s (blue)" % (redUser, redPlayer, blueUser, bluePlayer))
    
    # Ensure there is a spot for the traces (and other output) and that the files are world readable...
    makeTraceDir(logger)
    umask(002)

    # Format the command string and start and wait for the subprocesses...
    if testing:
        runtemplate = 'applications/icypc/private/domatch %s %s %s/%d-trace.txt'
        command = (runtemplate % (dochallenge.script(redUser, redPlayer), dochallenge.script(blueUser, bluePlayer), TRACES_DIR, id)).split()
    else:
        runtemplate = 'java -jar %s -player pipe 1 %s -player pipe 1 %s -view trace %s/%d-trace.txt'
        map = chooseMap()
        runtemplate += ' -map %s' % map
        logger.info('\tusing map %s' % map)
        command = (runtemplate % (ICYPC_JAR, dochallenge.script(redUser, redPlayer), dochallenge.script(blueUser, bluePlayer), TRACES_DIR, id)).split()
    p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (output, stderr) = p.communicate()
        
    # Parse the returned output, looking for winners, losers, and tie score
    m = output_pattern.match(output)
    if m:  # parsing OK, we have a winner and loser (or tie)
        red_score = '[%s (%s %s)]' % (m.group(3), m.group(4), m.group(5))
        blue_score = '[%s (%s %s)]' % (m.group(6), m.group(7), m.group(8))

        if m.group(2) == '0':  # red (challenger) wins
            winnerColor = 'red'
            (winner, loser) = ((redUser, redPlayer), (blueUser, bluePlayer))
            (winner_score, loser_score) = (m.group(3), m.group(6))
        elif m.group(2) == '1':  # blue (defender) wins
            winnerColor = 'blue'
            (winner, loser) = ((blueUser, bluePlayer), (redUser, redPlayer))
            (winner_score, loser_score) = (m.group(6), m.group(3))
        else:  # Tie
            winnerColor = "Tie"
            
        # Lookup index of winner and loser, record in match
        if winnerColor == 'Tie':
            challenger.update_record(status='resting')
            logger.info('\tTIE')
            db(db.match.id == id).delete()
        else:
            w = db((db.competitor.name_user == winner[0]) & (db.competitor.name_player == winner[1])).select().first()
            l = db((db.competitor.name_user == loser[0]) & (db.competitor.name_player == loser[1])).select().first()
            db(db.match.id == id).update(winner_color=winnerColor, winner=w, loser=l, output=(red_score + ' to ' + blue_score))
            if winner == (redUser, redPlayer):  # challenger wins
                logger.info('\tchallenger WINS')
            else:  # challenger loses
                logger.info('\tchallenger LOSES')
                if loser_score == '0':  # if challenger loses and scores 0, then to the bench
                    challenger.update_record(status='benched')
                    logger.info('\tchallenger scores 0 and is BENCHED.')
                else:  # otherwise, just rest
                    challenger.update_record(status='resting')
                    logger.info('\tchallenger RESTS')

        # Write stdout, stderr, and info to trace directory files
        try:
            f = open('%s/%s-stdout.txt' % (TRACES_DIR, id), 'w'); f.write(output); f.close()
            f = open('%s/%s-stderr.txt' % (TRACES_DIR, id), 'w'); f.write(stderr); f.close()
            info = "%s/%s (red) vs. %s/%s (blue)\n" % (redUser, redPlayer, blueUser, bluePlayer)
            f = open('%s/%s-info.txt' % (TRACES_DIR, id), 'w'); f.write(info); f.close()
        except:
            logger.critical('could not write stdout/err files in %s--tell a friend' % TRACES_DIR)
    else: # parsing failed, bench the challenger (just in case), delete the match, but log it
        challenger.update_record(status='benched')
        logger.info('\tmatch output parse failure; benching challenger.')
        db(db.match.id == id).delete()
        logger.error('no winner found in stdout; probably a script was not executable; stdout and stderr follow')
        logger.error('\n===== begin stdout =====\n%s\n===== end stdout =====' % output)
        logger.error('\n===== begin stderr =====\n%s\n===== end stderr =====' % stderr)

    # All done, just commit the changes
    db.commit()

def benchMissingPlayers():
    players = db(db.competitor.status != 'benched').select(db.competitor.ALL, orderby=db.competitor.id)
    for player in players:
        (u, p) = (player.name_user, player.name_player)
        if not dochallenge.scriptRunnable(logger, u, p):
            logger.info('\tmissing player (%s,%s) being BENCHED' % (u, p))
            player.update_record(status='benched')
    db.commit()

def runqueue():
    benchMissingPlayers()
    changed = False
    match = getMatch()
    while match != None:
        changed = True
        runMatch(match)
        match = getMatch()
    return changed

import time

while True:
    if runqueue():
        logger.info("Queue run complete; sleeping...")
    time.sleep(15)

